<?php

/**
*	Code highlight Library - builds the workings to highlight a file on the server
*
*	@author			Timothy Wood (codearachnid@gmail.com)
*	@last_modified	August 4th, 2007
*
*/

class Codehighlight {

	var $site;
	var $filename;
	var $root_folder;
	var $code;
	var $lines;
	var $output;
	var $previous;
  
	function Codehighlight() {
		$this->site =& get_instance();
		$this->previous = FALSE;
		$this->root_folder 	= ($this->isempty(trim($this->site->config->item('root_folder')))) 
										? './'
										: $this->site->config->item('root_folder');
										
    ini_set('highlight.comment', 'comment');
    ini_set('highlight.default', 'default');
    ini_set('highlight.keyword', 'keyword');
    ini_set('highlight.string', 'string');
    ini_set('highlight.html', 'html');
	}
	
	function set_file($file_to_read) {
    if ($this->root_folder[strlen($this->root_folder)-1] != '/') $this->root_folder .= '/';
    if ($file_to_read[0] == '.') $this->root_folder = '';
    $this->filename = $this->root_folder . $file_to_read;
    $this->get_contents();
    $this->clean_code();
  }
  
  function get_contents() {
    $this->code = file_get_contents($this->filename);
  }

  function clean_code() {
    /* Highlight String */
    $this->code = highlight_string($this->code, TRUE);
      
    /* Clean Up */
    $this->code = substr($this->code, 33, -15);
    $this->code = str_replace('&nbsp;', ' ', $this->code);
    $this->code = str_replace('&amp;', '&#38;', $this->code);
    $this->code = str_replace('<br />', "\n", $this->code);
    $this->code = str_replace('<span style="color: ', '<span class="', $this->code);
    $this->code = trim($this->code);
      
    /* Normalize Newlines */
    $this->code = str_replace("\r", "\n", $this->code);
    $this->code = preg_replace("!\n\n\n+!", "\n\n", $this->code);

    /* blow code to lines */
    $this->lines = explode("\n", $this->code);
  }
  
  function build_code() {
    /* Output Listing */
    $code_html = " <pre class=\"code\">\n";
    foreach ($this->lines as $key => $line) {
      if (substr($line, 0, 7) == '</span>') {
        $this->previous = FALSE;
        $line = substr($line, 7);
      }
    
      if (empty($line)) {
        $line = '&#160;';
      }
    
      if ($this->previous) {
        $line = "<span class=\"$this->previous\">" . $line;
      }
    
      /* Set Previous Style */
      if (strpos($line, '<span') !== FALSE) {
        switch (substr($line, strrpos($line, '<span') + 13, 1)) {
          case 'c':
            $this->previous = 'comment';
            break;
          case 'd':
            $this->previous = 'default';
            break;
          case 'k':
            $this->previous = 'keyword';
            break;
          case 's':
            $this->previous = 'string';
            break;
        }
      }
      
      /* Unset Previous Style Unless Span Continues */
      if (substr($line, -7) == '</span>') {
        $this->previous = FALSE;
      } elseif ($this->previous) {
        $line .= '</span>';
      }
      
      $line = $this->function_link($line);
      
      $code_html .= "<code>$line</code>\n";

    }
      $code_html .= " </pre>\n";
      return $code_html;
  }
  
  function build_ol() {
    /* Output Listing */
    $ordered_html = " <ol class=\"code\">\n";
    foreach ($this->lines as $key => $line) {
      if (substr($line, 0, 7) == '</span>') {
        $this->previous = FALSE;
        $line = substr($line, 7);
      }
    
      if (empty($line)) {
        $line = '&#160;';
      }
    
      if ($this->previous) {
        $line = "<span class=\"$this->previous\">" . $line;
      }
    
      /* Set Previous Style */
      if (strpos($line, '<span') !== FALSE) {
        switch (substr($line, strrpos($line, '<span') + 13, 1)) {
          case 'c':
            $this->previous = 'comment';
            break;
          case 'd':
            $this->previous = 'default';
            break;
          case 'k':
            $this->previous = 'keyword';
            break;
          case 's':
            $this->previous = 'string';
            break;
        }
      }
      
      /* Unset Previous Style Unless Span Continues */
      if (substr($line, -7) == '</span>') {
        $this->previous = FALSE;
      } elseif ($this->previous) {
        $line .= '</span>';
      }
      
      $line = $this->function_link($line);
      
      $num = $key + 1;
      if ($key % 2) {
        $ordered_html .= " <li id=\"line-$num\" class=\"even\"><code>$line</code></li>\n";
      } else {
        $ordered_html .= " <li id=\"line-$num\"><code>$line</code></li>\n";
      }
    }
      $ordered_html .= " </ol>\n";
      return $ordered_html;
  }

  function function_link($test_string) {
    $linked_string = '';
    //$manual = 'http://www.php.net/function.';
    $manual = 'http://www.php.net/';

    $linked_string = preg_replace(
        // Match a highlighted keyword
        '~([\w_]+)(\s*</span>)'.
        // Followed by a bracket
        '(\s*<span\s+class="' . $this->previous . '">\s*\()~m',
        // Replace with a link to the manual
        '<a href="' . $manual . '$1" target="_blank">$1</a>$2$3', $test_string);

    return $linked_string;
  }

  function display_code() {
    return $this->build_code();
  }
  
  function display_ol() {
    return $this->build_ol();
  }
  
  function display_all($include_assets = TRUE) {
    //$build_view = ($include_assets) ? $this->display_css() . $this->display_js() . $this->display_code() . $this->display_ol() : $this->display_code() . $this->display_ol();
    $build_view = ($include_assets) ? $this->display_css() . $this->display_js() . $this->display_code() : $this->display_code();
    return $build_view;
  }
  
  function display_js() {
    $js = '<script type="text/javascript">
            function createElement(element) {
            	element = element.toLowerCase();
            	if (typeof document.createElementNS != \'undefined\')
            		return document.createElementNS(\'http://www.w3.org/1999/xhtml\', element);
            	if (typeof document.createElement != \'undefined\')
            		return document.createElement(element);
            	return false;
            }
            
            function addClass(el,val) {
            	if (!el.className) el.className = val;
            	else {
            		newClassName = el.className + " " + val;
            		el.className = newClassName;
            	}
            }
            
            function insertAfter(newElement,targetElement) {
            	var parent = targetElement.parentNode;
            	if (parent.lastChild == targetElement) parent.appendChild(newElement);
            	else parent.insertBefore(newElement,targetElement.nextSibling);
            }
            
            function addLoadEvent(func) {
              var oldonload = window.onload;
              if (typeof window.onload != \'function\') window.onload = func;
              else {
                window.onload = function() {
                  if (oldonload) oldonload();
                  func();
                }
              }
            }
            
            /* --- code view --- */
            
            // set initial view (\'pre\' or \'ol\', defaults to \'pre\')
            var initialView = \'ol\';
            var classCodePre = \'code\';
            
            function initCodeView() {
            	if (!document.getElementsByTagName) return;
            
            	// find all pre elements
            	var els = document.getElementsByTagName("pre");
            	var numEls = els.length;
            	for (var i=0;i<numEls;i++) {
            		if (els[i].className && els[i].className.match(classCodePre)) {
            			// create, hide and append code list
            			var elAlt = createCodeList(els[i]);
            
            			if (initialView === \'ol\') els[i].style.display = \'none\';
            			else elAlt.style.display = \'none\';
            			insertAfter(elAlt,els[i]);
            
            			// build toggle control
            			var elToggle = createElement("a");
            			elToggle.appendChild(document.createTextNode("Toggle Code View"));
            			elToggle.setAttribute(\'href\',"javascript:;");
            
            			// add toggle event
            			elToggle.onclick = function (e) {
            				toggleCodeView(this);
            			}
            			if (elToggle.captureEvents) elToggle.captureEvents(Event.ONCLICK);
            
            			// append control to paragraph and insert before the list
            			var elToggleWrap = createElement("p");
            			if (initialView === \'ol\') addClass(elToggleWrap,"contract");
            			else addClass(elToggleWrap,"expand");
            			elToggleWrap.appendChild(elToggle);
            			els[i].parentNode.insertBefore(elToggleWrap,els[i]);
            		}
            	}
            }
            
            // creates an ordered list as the alternative display for preformatted lines in code elements
            // note: it is impractical to use line returns to split lines in IE, so use a code element for each line in the pre
            function createCodeList(elCodePre) {
            	var elCodeOl = createElement("ol");
            	addClass(elCodeOl,\'code\');
            
            	// iterate through list to grab lines of code
            	var odd = true;
            	var elsNodes = elCodePre.childNodes;
            	var numNodes = elsNodes.length;
            	for (var i=0;i<numNodes;i++) {
            		// if we have a line of code, grab it (cheat a little and grab innerHTML)
            		if (elsNodes[i].nodeName.toLowerCase() === \'code\') {
            			var elCode = createElement("code");
            			elCode.innerHTML = elsNodes[i].innerHTML;
            
            			var elCodeLi = createElement("li");
            			elCodeLi.appendChild(elCode);
            
            			// alternate the classes
            			if (odd) {
            				addClass(elCodeLi,\'odd\');
            				odd = false;
            			} else odd = true;
            
            			elCodeOl.appendChild(elCodeLi);
            		}
            	}
            
            	return elCodeOl;
            }
            
            function toggleCodeView(control) {
            	var elOrig = control.parentNode.nextSibling;
            	var elAlt = elOrig.nextSibling;
            
            	if (elAlt.style.display == \'none\') {
            		elOrig.style.display = \'none\';
            		elAlt.style.display = \'block\';
            		control.parentNode.className = "contract";
            	}else{
            		elAlt.style.display = \'none\';
            		elOrig.style.display = \'block\';
            		control.parentNode.className = "expand";
            	}
            }
            
            function doCodeView(el) {
                var els = el.childNodes;
                var numEls = els.length;
            
                // find all pre elements
                for (var i=0;i<numEls;i++) {
                    if ((els[i].nodeName.toLowerCase() == "pre") && els[i].className && els[i].className.match(classCodePre)) {
                        // create, hide and append code list
                        var elAlt = createCodeList(els[i]);
            
                        if (initialView === \'ol\') els[i].style.display = \'none\';
                        else elAlt.style.display = \'none\';
                        insertAfter(elAlt,els[i]);
            
                        // build toggle control
                        var elToggle = createElement("a");
                        elToggle.appendChild(document.createTextNode("Toggle Code View"));
                        elToggle.setAttribute(\'href\',"javascript:;");
            
                        // add toggle event
                        elToggle.onclick = function (e) {
                            toggleCodeView(this);
                        }
                        if (elToggle.captureEvents) elToggle.captureEvents(Event.ONCLICK);
            
                        // append control to paragraph and insert before the list
                        var elToggleWrap = createElement("p");
                        if (initialView === \'ol\') addClass(elToggleWrap,"contract");
                        else addClass(elToggleWrap,"expand");
                        elToggleWrap.appendChild(elToggle);
                        els[i].parentNode.insertBefore(elToggleWrap,els[i]);
                    }
                }
            }
            
            addLoadEvent(initCodeView);
          </script>';    
    return $js;
  }
  
  function display_css() {
    $css = '<style type="text/css" media="screen">
              body {
                margin:2em;
                padding: 0;
                border:0;
                font:1em verdana, helvetica, sans-serif;
                color:#000;
                background:#fff;
                text-align:center;
                }
              pre.code {
                width:90%;
                margin:0 5%;
                padding:0;
                font-size:0.75em;
                line-height:1.8em;
                overflow:hidden;
                color:#939399;
                text-align:left;
                list-style-position:inside;
                border:1px solid #d3d3d0;
              }
              pre.code code {
                font:1.2em courier, monospace;
                color:#c30;
                white-space:pre;
                padding-left:0.5em;
                }
              ol.code {
                width:90%;
                margin:0 5%;
                padding:0;
                font-size:0.75em;
                line-height:1.8em;
                overflow:hidden;
                color:#939399;
                text-align:left;
                list-style-position:inside;
                border:1px solid #d3d3d0;
                }
              ol.code li {
                float:left;
                clear:both;
                width:99%;
                white-space:nowrap;
                margin:0;
                padding:0 0 0 1%;
                background:#fff;
                }
              ol.code li.odd { background:#f3f3f0; }
              ol.code li code {
                font:1.2em courier, monospace;
                color:#c30;
                white-space:pre;
                padding-left:0.5em;
                }
              .code .comment { color:#939399; }
              .code .default { color:#44c; }
              .code .keyword { color:#373; }
              .code .string { color:#c30; }
            </style>';
    
    return $css;
  }
	
	/*
	 * 
	 * internal functions needed for the class 
	 * 
	 */
	function isempty($var) {
	  if (((is_null($var) || rtrim($var) == "") && $var !== false) || (is_array($var) && empty($var))) {
	    return true;
	  } else {
	    return false;
	  }
	} 
	
}

?>
